use chrono::{DateTime, Local};
use sqlx::PgPool;

use crate::{db, filter, model, Error, Result};

pub async fn grab_add(
    p: &PgPool,
    m: model::url::Url,
    max_seed: Option<u32>,
) -> Result<Option<model::url::Url>> {
    db::url::grab_insert(p, m, max_seed)
        .await
        .map_err(Error::from)
}

pub async fn add(p: &PgPool, m: &model::url::Url) -> Result<String> {
    let mut tx = p.begin().await.map_err(Error::from)?;

    let exists = match db::url::is_exists(&mut *tx, &m.url).await {
        Ok(v) => v,
        Err(e) => {
            tx.rollback().await.map_err(Error::from)?;
            return Err(Error::from(e));
        }
    };

    if exists {
        return Err(Error::already_exists("链接已存在"));
    }

    let id = match db::url::insert(&mut *tx, m).await {
        Ok(v) => v,
        Err(e) => {
            tx.rollback().await.map_err(Error::from)?;
            return Err(Error::from(e));
        }
    };

    tx.commit().await.map_err(Error::from)?;

    Ok(id)
}

pub async fn edit(p: &PgPool, m: &model::url::Url, skip_update_password: bool) -> Result<u64> {
    if m.id.is_empty() {
        return Err(Error::invalid_parameter("未指定ID"));
    }

    db::url::update(p, m, skip_update_password)
        .await
        .map_err(Error::from)
}

pub async fn edit_password(p: &PgPool, id: &str, password: &str) -> Result<u64> {
    db::url::update_password(p, id, password)
        .await
        .map_err(Error::from)
}

pub async fn edit_expired(p: &PgPool, id: &str, expired: Option<DateTime<Local>>) -> Result<u64> {
    db::url::update_expired(p, id, expired)
        .await
        .map_err(Error::from)
}

pub async fn increment_hit(p: &PgPool, url: &str) -> Result<u64> {
    db::url::increment_hit(p, url).await.map_err(Error::from)
}

pub async fn del(p: &PgPool, id: &str, user_id: Option<&str>) -> Result<u64> {
    db::url::del(p, id, user_id).await.map_err(Error::from)
}

pub async fn find<'a>(p: &PgPool, f: &filter::url::FindBy<'a>) -> Result<Option<model::url::Url>> {
    db::url::find(p, f).await.map_err(Error::from)
}

pub async fn list<'a>(
    p: &PgPool,
    f: &filter::url::List<'a>,
) -> Result<db::Pagination<model::url::Url>> {
    let mut tx = p.begin().await.map_err(Error::from)?;

    let total = match db::url::list_count(&mut *tx, f).await {
        Ok(v) => v,
        Err(e) => {
            tx.rollback().await.map_err(Error::from)?;
            return Err(Error::from(e));
        }
    };

    let data = match db::url::list_data(&mut *tx, f).await {
        Ok(v) => v,
        Err(e) => {
            tx.rollback().await.map_err(Error::from)?;
            return Err(Error::from(e));
        }
    };

    tx.commit().await.map_err(Error::from)?;

    Ok(db::Pagination::quick(total, &f.pagination, data))
}

pub async fn visit(p: &PgPool, url: &str, ip: &str, user_agent: &str) -> Result<model::url::Url> {
    let mut tx = p.begin().await.map_err(Error::from)?;

    let m = match db::url::find(&mut *tx, &filter::url::FindBy::URL(url)).await {
        Ok(v) => v,
        Err(e) => {
            tx.rollback().await.map_err(Error::from)?;
            return Err(Error::from(e));
        }
    };

    let m = match m {
        Some(v) => v,
        None => {
            tx.rollback().await.map_err(Error::from)?;
            return Err(Error::not_found("不存在的链接"));
        }
    };

    let now = Local::now();
    if m.has_expired {
        if &m.expired >= &now {
            tx.rollback().await.map_err(Error::from)?;
            return Err(Error::not_found("已过期"));
        }
    }

    if let Err(e) = db::url::increment_hit(&mut *tx, url).await {
        tx.rollback().await.map_err(Error::from)?;
        return Err(Error::from(e));
    }

    if let Err(e) = db::url_log::insert(
        &mut *tx,
        &model::url::UrlLogs {
            url: url.into(),
            ip: ip.into(),
            user_agent: user_agent.into(),
            dateline: now,
            ..Default::default()
        },
    )
    .await
    {
        tx.rollback().await.map_err(Error::from)?;
        return Err(Error::from(e));
    }

    tx.commit().await.map_err(Error::from)?;
    Ok(m)
}

pub async fn find_log(p: &PgPool, id: &str) -> Result<Option<model::url::UrlWithLogs>> {
    db::url_log::find(p, id).await.map_err(Error::from)
}

pub async fn list_log<'a>(
    p: &PgPool,
    f: &filter::url::ListLogs<'a>,
) -> Result<db::Pagination<model::url::UrlWithLogs>> {
    let total = db::url_log::list_count(p, f).await.map_err(Error::from)?;
    let data = db::url_log::list_data(p, f).await.map_err(Error::from)?;
    Ok(db::Pagination::quick(total, &f.pagination, data))
}

pub async fn find_log_with_user(
    p: &PgPool,
    id: &str,
) -> Result<Option<model::user_url_log::UserUrlWithLog>> {
    db::user_url_log::find(p, id).await.map_err(Error::from)
}

pub async fn list_log_with_user<'a>(
    p: &PgPool,
    f: &filter::url::ListLogs<'a>,
) -> Result<db::Pagination<model::user_url_log::UserUrlWithLog>> {
    let total = db::user_url_log::list_count(p, f)
        .await
        .map_err(Error::from)?;
    let data = db::user_url_log::list_data(p, f)
        .await
        .map_err(Error::from)?;
    Ok(db::Pagination::quick(total, &f.pagination, data))
}
